/**
 * Copyright 2012 ibiblio
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ibiblio.terasaur.item

import grails.plugins.springsecurity.Secured
import org.ibiblio.terasaur.user.User
import org.ibiblio.terasaur.item.Item
import javax.servlet.http.HttpServletResponse
import org.ibiblio.terasaur.controller.ParamsHelper
import org.ibiblio.terasaur.search.DateHelper
import org.ibiblio.terasaur.item.Collection
import org.ibiblio.terasaur.ui.UrlHelper
import org.ibiblio.terasaur.item.PermissionHelper
import org.ibiblio.terasaur.controller.ItemMixin
import org.grails.plugins.springsecurity.service.acl.AclUtilService
import org.springframework.security.access.AccessDeniedException

// TODO: Refactor low-level ACL interactions into a mixin or helper class
import org.codehaus.groovy.grails.plugins.springsecurity.acl.AclEntry
import org.codehaus.groovy.grails.plugins.springsecurity.acl.AclSid
import org.springframework.security.acls.domain.BasePermission

@Mixin(ItemMixin)
class ItemController {
    static defaultAction = "list"
    boolean _flushTransactions = false // Option for unit testing
    def scaffold = Item
    def springSecurityService // injected
    def itemService // injected
    def aclUtilService // injected
    static ParamsHelper _paramsHelper = new ParamsHelper()
    static UrlHelper urlHelper = new UrlHelper()
    static PermissionHelper permissionHelper = new PermissionHelper()

    def index = {
        redirect(action: "list")
    }

    def list = {
        redirect(controller:"browse", action:"recent")
    }

    def show = {
        def item = getItemInstance()
        def pageTitle
        def downloadsList = null
        boolean canEdit = false
        def setPublishedLabel = null
        def setPublishedDate = null
        def setPublishedOnclick = null
        def collectionBrowseLink = null

        // TODO: check request.forwardURI, compare to canonical link, conditionally
        // 		 issue 301 redirect.  Implement this if we see requests to different urls.

        if (item) {
            pageTitle = item.title
            canEdit = canEditItem(item)
            def downloadsParams = [
                'offset': 0,
                'max': 10,
                'item': item,
                'canEditItem': canEdit
                ]
            downloadsList = itemService.downloads(downloadsParams)
            collectionBrowseLink = urlHelper.collectionLink(item.collection, 'browse')
        } else {
            flash.message = message(code: 'item.not.found.message')
            pageTitle = message(code: 'item.not.found.title')
        }

        if (canEdit) {
            // Publish/unpublish logic
            if (item.published.isAfter(DateHelper.now())) {
                setPublishedLabel = 'publish'
                setPublishedDate = DateHelper.nowLocal().toString(DateHelper.getPublishedFormatter())
                setPublishedOnclick = ''
            } else {
                setPublishedLabel = 'unpublish'
                setPublishedDate = DateHelper.UNPUBLISH_DATE
                setPublishedOnclick = 'return confirm(\'' + message(code:'item.unpublish.confirm.message') + '\');'
            }
        }

        [
            pageTitle: pageTitle,
            itemInstance: item,
            downloadsList: downloadsList,
            canEditItem: canEdit,
            canonicalLink: urlHelper.canonicalLink(item, 'show'),
            downloadsLink: urlHelper.canonicalLink(item, 'downloads'),
            collectionBrowseLink: collectionBrowseLink,
            collectionLogo: getCollectionLogo(item),
            setPublishedLabel: setPublishedLabel,
            setPublishedDate: setPublishedDate,
            setPublishedOnclick: setPublishedOnclick
        ]
    }

    def downloads = {
        def item = getItemInstance()
        def pageTitle
        def downloadsList
        boolean canEdit

        if (item) {
            pageTitle = item.title + ' - downloads list'
            canEdit = canEditItem(item)
            def listParams = _paramsHelper.getOffsetMax(params)
            listParams.put('item', item)
            listParams.put('canEditItem', canEdit)
            downloadsList = itemService.downloads(listParams)
        } else {
            flash.message = message(code: 'item.not.found.message')
            pageTitle = message(code: 'item.not.found.title')
            downloadsList = null
            canEdit = false
        }

        def map = [
            pageTitle: pageTitle,
            itemInstance: item,
            downloadsList: downloadsList,
            canEditItem: canEdit,
            collectionLogo: getCollectionLogo(item)
        ]

        render(view:"/torrent/list", model:map)
    }

    @Secured(['ROLE_CAN_CREATE_ITEM', 'IS_AUTHENTICATED_REMEMBERED'])
    def edit = {
        def item = getItemInstance()
        def published = null

        if (item) {
            if (!canEditItem(item)) {
                setAccessDeniedRedirect()
                return
            }
            published = item.published.toString(DateHelper.getPublishedFormatter())
        } else {
            flash.message = message(code: 'item.not.found.message')
        }

        return [
            itemInstance: item,
            publishedDate: published,
            editFormAction : 'update',
            pageTitle: 'Edit item',
            collectionList: _getCollectionList()
        ]
    }

    /**
     * Generate list of available collections for the user.  Admins can put
     * items in any collections.  Those with write permission on a collection
     * can add items to the collection.  Anyone can use the General collection.
     *
     * @return
     */
    private List _getCollectionList() {
        List collections
        if (permissionHelper.isAdmin(springSecurityService.currentUser)) {
            collections = Collection.list()
        } else {
            collections = _getAuthorizedCollections()
            // Add General collection
            collections << Collection.get(1)
        }
        return collections
    }

    /**
     * Get list of collections where a user has WRITE permission.   These
     * are the collections into which the user is allowed to add items.
     *
     * @return
     */
    private List _getAuthorizedCollections() {
        AclSid sid = AclSid.findBySid(springSecurityService.currentUser.username)
        // If we didn't find a sid, it means the user has no acl entries yet.
        if (sid == null) {
            return []
        }

        String query = "FROM AclEntry \
            WHERE sid.id = :sid \
                AND mask = :mask \
                AND aclObjectIdentity.aclClass.className = :className)"
        Map queryParams = [sid:sid.id, className:Collection.class.name, mask:BasePermission.WRITE.mask]
        List<AclEntry> entries = AclEntry.executeQuery(query, queryParams)

        List collections = []
        entries.each {entry->
            collections << Collection.get(entry.aclObjectIdentity.objectId)
        }
        return collections
    }

    /**
     * Use the save view for create to allow handling of creates and updates
     * in the save action.
     */
    @Secured(['ROLE_CAN_CREATE_ITEM', 'IS_AUTHENTICATED_REMEMBERED'])
    def create = {
        def published = DateHelper.nowLocal().toString(DateHelper.getPublishedFormatter())
        def map = [
            editFormAction : 'save',
            pageTitle: 'Add new item',
            publishedDate: published,
            collectionList: _getCollectionList()
            ]
        render(view:"save", model:map)
    }

    @Secured(['ROLE_CAN_CREATE_ITEM', 'IS_AUTHENTICATED_REMEMBERED'])
    def save = {
        Item item = null
        boolean success = false
        try {
            Map result = itemService.save(params, springSecurityService.currentUser)
            item = result['item']
            success = result['success']
        } catch (Exception e) {
            log.error("ItemService save exception (" + e.class.name + "): " + e.message)
        }

        if (success) {
            redirect(uri: urlHelper.canonicalLink(item, 'show'))
        } else {
            flash.error = message(code:getItemSaveErrorCode(item))
            [
                editFormAction : 'save',
                pageTitle: 'Add new item',
                itemInstance: item,
                publishedDate: params.published,
                collectionList: _getCollectionList()
            ]
        }
    }

    @Secured(['ROLE_CAN_CREATE_ITEM', 'IS_AUTHENTICATED_REMEMBERED'])
    /**
     * Update action covers updating metadata, deleting, undeleting,
     * and unpublishing.  See _getUpdateSuccessMessageCode for relevant
     * message codes.  Note that all item update messages must contain
     * an item title slot as the {0} argument.
     */
    def update = {
        def item = getItemInstance()
        if (item) {
            try {
                def success = itemService.update(item, params)
                if (success) {
                    flash.message = message(code:_getUpdateSuccessMessageCode(), args:[item.title])
                    redirect(uri: urlHelper.canonicalLink(item, 'show'))
                } else {
                    if (item.hasErrors()) {
                        flash.error = message(code:getItemSaveErrorCode(item))
                        flash.error += g.renderErrors(bean:item)
                    } else {
                        flash.error = message(code:'item.update.failed.message', args:[item.title])
                    }
                    redirect(uri: urlHelper.canonicalLink(item, 'edit'))
                }
            } catch (AccessDeniedException e) {
                setAccessDeniedRedirect()
            }
        } else {
            flash.message = message(code: 'default.not.found.message', args: ['Item', params.id])
            redirect(action: "list")
        }
    }

    private String _getUpdateSuccessMessageCode() {
        String code
        if (params.deleted && params.deleted == '1') {
            code = 'item.deleted.message'
        } else if (params.deleted && params.deleted == '0') {
            code = 'item.undeleted.message'
        } else if (params.published && params.published == DateHelper.UNPUBLISH_DATE) {
            code = 'item.unpublished.message'
        } else {
            code = 'item.updated.message'
        }
        return code
    }

    /**
     * Legacy item page redirect for torrent.ibiblio.org
     */
    def legacy_redirect = {
        def item = getItemInstance()
        def redirectUri
        if (params.torrents) {
            redirectUri = urlHelper.canonicalLink(item, 'downloads')
        } else {
            redirectUri = urlHelper.canonicalLink(item, 'show')
        }
        def location = urlHelper.serverUrl(request) + redirectUri

        // controller.redirect() doesn't support 301 redirects in 1.3.x
        response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY)
        response.setHeader('Location', location)
        response.setHeader('Connection', 'close')
        render('')
        return false
    }
}
